Aprenda a implementar Error Boundaries no React com hooks para lidar com erros de carregamento de recursos, melhorando a experiência do usuário e a estabilidade da aplicação.
Carregamento Robusto de Recursos em React: Dominando Error Boundaries com Hooks
Em aplicações web modernas, carregar recursos de forma assíncrona é uma prática comum. Seja buscando dados de uma API, carregando imagens ou importando módulos, lidar com erros potenciais durante o carregamento de recursos é crucial para uma experiência de usuário tranquila. Os Error Boundaries do React fornecem um mecanismo para capturar erros de JavaScript em qualquer lugar na árvore de componentes filhos, registrar esses erros e exibir uma UI de fallback em vez de quebrar toda a aplicação. Este artigo explora como usar efetivamente os Error Boundaries em conjunto com os React Hooks para gerenciar erros de carregamento de recursos.
Entendendo os Error Boundaries
Antes do React 16, erros de JavaScript não tratados durante a renderização de componentes corrompiam o estado interno do React e causavam erros enigmáticos em renderizações subsequentes. Os Error Boundaries resolvem isso agindo como blocos 'catch-all' para erros que ocorrem em seus componentes filhos. Eles são componentes React que implementam um ou ambos os seguintes métodos de ciclo de vida:
static getDerivedStateFromError(error): Este método estático é invocado após um erro ser lançado por um componente descendente. Ele recebe o erro que foi lançado como argumento e retorna um valor para atualizar o estado do componente.componentDidCatch(error, info): Este método de ciclo de vida é invocado após um erro ser lançado por um componente descendente. Ele recebe o erro que foi lançado como argumento, bem como um objeto contendo informações sobre qual componente lançou o erro. Você pode usá-lo para registrar informações do erro.
É importante notar que os Error Boundaries só capturam erros na fase de renderização, em métodos de ciclo de vida e nos construtores de toda a árvore abaixo deles. Eles não capturam erros para:
- Manipuladores de eventos (saiba mais na seção abaixo)
- Código assíncrono (ex:
setTimeoutourequestAnimationFramecallbacks) - Renderização no lado do servidor
- Erros lançados no próprio Error Boundary (em vez de em seus filhos)
Error Boundaries e React Hooks: Uma Combinação Poderosa
Enquanto componentes de classe eram tradicionalmente usados para implementar Error Boundaries, os React Hooks oferecem uma abordagem mais concisa e funcional. Podemos criar um hook reutilizável useErrorBoundary que encapsula a lógica de tratamento de erros e fornece uma maneira conveniente de envolver componentes que podem lançar erros durante o carregamento de recursos.
Criando um Hook useErrorBoundary Personalizado
Aqui está um exemplo de um hook useErrorBoundary:
import { useState, useCallback } from 'react';
function useErrorBoundary() {
const [error, setError] = useState(null);
const resetError = useCallback(() => {
setError(null);
}, []);
const captureError = useCallback((e) => {
setError(e);
}, []);
const ErrorBoundary = useCallback(({ children, fallback }) => {
if (error) {
return fallback ? fallback : Ocorreu um erro: {error.message || String(error)};
}
return children;
}, [error]);
return { ErrorBoundary, captureError, error, resetError };
}
export default useErrorBoundary;
Explicação:
useState: UsamosuseStatepara gerenciar o estado do erro. Ele define inicialmente o erro comonull.useCallback: UsamosuseCallbackpara memoizar as funçõesresetErrorecaptureError. Esta é uma boa prática para evitar re-renderizações desnecessárias se essas funções forem passadas como props.- Componente
ErrorBoundary: Este é um componente funcional criado comuseCallbackque recebechildrene uma prop opcionalfallback. Se um erro existir no estado, ele renderiza o componentefallbackfornecido ou uma mensagem de erro padrão. Caso contrário, ele renderiza os filhos. Isso atua como nosso Error Boundary. O array de dependências `[error]` garante que ele seja re-renderizado quando o estado `error` mudar. - Função
captureError: Esta função é usada para definir o estado do erro. Você a chamará dentro de um blocotry...catchao carregar recursos. - Função
resetError: Esta função limpa o estado do erro, permitindo que o componente re-renderize seus filhos (potencialmente tentando novamente o carregamento do recurso).
Implementando o Carregamento de Recursos com Tratamento de Erros
Agora, vamos ver como usar este hook para lidar com erros de carregamento de recursos. Considere um componente que busca dados do usuário de uma API:
import React, { useState, useEffect } from 'react';
import useErrorBoundary from './useErrorBoundary';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const { ErrorBoundary, captureError, error, resetError } = useErrorBoundary();
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`Erro HTTP! status: ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (e) {
captureError(e);
}
};
fetchData();
}, [userId, captureError]);
if (error) {
return (
Falha ao carregar dados do usuário. {user.name}
Email: {user.email}
{/* Outros detalhes do usuário */}Explicação:
- Nós importamos o hook
useErrorBoundary. - Nós chamamos o hook para obter o componente
ErrorBoundary, a funçãocaptureError, o estadoerrore a funçãoresetError. - Dentro do hook
useEffect, envolvemos a chamada da API em um blocotry...catch. - Se um erro ocorrer durante a chamada da API, chamamos
captureError(e)para definir o estado do erro. - Se o estado
errorestiver definido, renderizamos o componenteErrorBoundary. Nós fornecemos uma propfallbackpersonalizada que exibe uma mensagem de erro e um botão "Tentar novamente". Clicar no botão chamaresetErrorpara limpar o estado do erro, acionando uma re-renderização e outra tentativa de buscar os dados. - Se nenhum erro ocorreu e os dados do usuário foram carregados, renderizamos os detalhes do perfil do usuário.
Lidando com Diferentes Tipos de Erros de Carregamento de Recursos
Diferentes tipos de erros de carregamento de recursos podem exigir diferentes estratégias de tratamento. Aqui estão alguns cenários comuns e como abordá-los:
Erros de Rede
Erros de rede ocorrem quando o cliente não consegue se conectar ao servidor (ex: devido a uma queda de rede ou a indisponibilidade de um servidor). O exemplo acima já lida com erros de rede básicos usando `response.ok`. Você pode querer adicionar uma detecção de erro mais sofisticada, por exemplo:
//Dentro da função fetchData
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
// Considere adicionar tratamento para códigos de erro específicos
if (response.status === 404) {
throw new Error("Usuário não encontrado");
} else if (response.status >= 500) {
throw new Error("Erro de servidor. Por favor, tente novamente mais tarde.");
} else {
throw new Error(`Erro HTTP! status: ${response.status}`);
}
}
const data = await response.json();
setUser(data);
} catch (error) {
if (error.message === 'Failed to fetch') {
// Provavelmente um erro de rede
captureError(new Error('Erro de rede. Por favor, verifique sua conexão com a internet.'));
} else {
captureError(error);
}
}
Neste caso, você pode exibir uma mensagem para o usuário indicando que há um problema de conectividade de rede e sugerir que ele verifique sua conexão com a internet.
Erros de API
Erros de API ocorrem quando o servidor retorna uma resposta de erro (ex: um 400 Bad Request ou um 500 Internal Server Error). Como mostrado acima, você pode verificar `response.status` e tratar esses erros apropriadamente.
Erros de Análise de Dados
Erros de análise de dados ocorrem quando a resposta do servidor não está no formato esperado e não pode ser analisada (ex: JSON inválido). Você pode lidar com esses erros envolvendo a chamada response.json() em um bloco try...catch:
//Dentro da função fetchData
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`Erro HTTP! status: ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (error) {
if (error instanceof SyntaxError) {
captureError(new Error('Falha ao analisar os dados do servidor.'));
} else {
captureError(error);
}
}
Erros de Carregamento de Imagem
Para o carregamento de imagens, você pode usar o manipulador de eventos onError na tag <img>:
function MyImage({ src, alt }) {
const { ErrorBoundary, captureError } = useErrorBoundary();
const [imageLoaded, setImageLoaded] = useState(false);
const handleImageLoad = () => {
setImageLoaded(true);
};
const handleImageError = (e) => {
captureError(new Error(`Falha ao carregar a imagem: ${src}`));
};
return (
Falha ao carregar a imagem.